
/* Generated by Interface Builder */

#import "MenuManager.h"
#import "ClassManager.h"
#import "WorkspaceManager.h"
#import "DictManager.h"
#import "GlyphView.h"
#import "NuString.h"
#import "NuWraps.h"
#import "FinderManager.h"
#import "MKManager.h"
#import "InstanceManager.h"
#import "BMList.h"
#import "Terminator.h"
#import "FGWindow.h"
#import "PrefsManager.h"
#import <appkit/Application.h>
#import <appkit/Panel.h>
#import <appkit/Matrix.h>
#import <appkit/ButtonCell.h>
#import <appkit/OpenPanel.h>
#import <appkit/SavePanel.h>
#import <appkit/ScrollView.h>
#import <appkit/Text.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <defaults/defaults.h>
#import <appkit/Font.h>
#import <objc/error.h>
#import <objc/List.h>
#import <objc/Storage.h>
// #import <objc/zone.h>
// #import <mach/mach_init.h>
#import <dpsclient/psops.h>
#import <streams/streams.h>
#import <libc.h>
#import <strings.h>
#import <ctype.h>
#import <libc.h>
#include <mach.h>
#include <sys/types.h>
#include <sys/stat.h>

extern id Nu ; // sole MenuManager instance 

// allocate a large, general purpose text buffer
char bigBuf[49152] ;

struct loadClass
{ char *className ;
  char *dotOFileName ;
} ;

const char *NuTypes[] = 
{ "Nu",
  NULL 
} ;

const char *openTypes[] = 
{ "m","Nu","rtf","rtfd",
  NULL 
} ;

// procedure to get the next line from a stream
// The newline is included (except at end of file)
void NuGets(NXStream *aStream, char * aBuf)
{ int c,i = 0;
  c = NXGetc(aStream) ;
  do
  { if(c == -1) // end of stream
    { aBuf[i] = '\0' ;
      return  ;
    }
    else if(c == '\n')
    { aBuf[i] = c ;
      aBuf[i+1] = '\0' ;
      return ;
    }
    else
    { aBuf[i++] = (char) c ;
      c = NXGetc(aStream) ;
    } 
  } while(1) ;
}

@implementation MenuManager

- (int) app:sender openFile: (const char *) filename type: (const char *) aType ;
{ // respond to remote message to open a file
  const char *fileList[2] ;
  fileList[0] = filename ;
  fileList[1] = NULL ;
  [self openList: fileList path: NULL] ;
  return 1 ;   // YES? NO ? 1?
}

- (BOOL) appAcceptsAnotherFile: sender ;
{ return YES ;
}

- appendFileToTranscript: (char *) fileName ;
{ // append the text of the indicated file to the transcript
  char *aStr ;
  struct stat statBuf ;
  int fd ;
  if(!(fd = open(fileName,O_RDONLY)))
  { NXRunAlertPanel("Nu","Cannot open: %c",NULL,NULL,NULL,fileName) ;
    return self ;
  }
  fstat(fd,&statBuf) ;
  aStr = malloc(statBuf.st_size + 1) ;
  read(fd,aStr,statBuf.st_size) ;
  aStr[statBuf.st_size] = '\0' ;
  [self printf: aStr] ;
  close(fd) ;
  free(aStr) ;
  return self ;
}


- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ // delegate method for browser object 
  if(sender == loadedClassesBrowser)
    return [[ClassManager loadList] count] ; 
  return 0 ;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{  if(sender == loadedClassesBrowser)
   { const char *aClass ;
     [cell setStringValue: [[[ClassManager loadList] objectAt: row] cString]];
     [cell setLeaf:YES] ;
     aClass = [cell stringValue] ;
     if(!strcmp(aClass,"Glyph") || !strcmp(aClass,"Root") 
     || !strcmp(aClass,"Tray") || !strcmp(aClass,"Error"))
     { [cell setSelectable: NO] ;
       [cell setEnabled: NO] ;
     }
   }
   return self ;
}


- clearTranscript: sender ;
{ [transcriptText setText: ""] ;
  return self ;
}

- debugWindows: sender ;
{ if(glyphView)
    [[glyphView rootGlyph] debugWindows] ;
  return self ;
}

- finder: sender ;
{ // bring up the Finder panel 
  [finderPanel makeKeyAndOrderFront: self] ;
   [[[NXGetNamedObject("FinderManager",NXApp) 
      findText] controlView] selectText: self] ;
  return self ;
}

- findLine: sender ;
{ // bring up the Finder panel with the line finder
  // textfield selected
  [finderPanel makeKeyAndOrderFront: self] ;
  [NXGetNamedObject("FinderManager",NXApp) finderCurrent: self] ;
  [[NXGetNamedObject("FinderManager",NXApp) findLine] selectText: self] ;
  return self ;
}



- findNext: sender ;
{ [self finder: sender] ;
  [NXGetNamedObject("FinderManager",NXApp) finderNext: sender] ;
  return self ;
}

- glyphView: anObject ;
{ // set the glyphView iv
  glyphView = anObject ;
  // update instanceManager's instance
  [instanceManager instance: [glyphView targetGlyph]] ;
  return self ;
}


- glyphManager ;
{ // get the glyph manager
  return glyphManager ;
}

- glyphManager: sender ;
{ // get the glyph browser
  [glyphBrowser makeKeyAndOrderFront: self] ;
  return self ;
}

- (NXZone *) glyphZone ;
{ // return the default zone used for glyph allocation
  return glyphZone ;
}

- glyphView ;
{ // returns the current (or most recently) selected glyphView
  return glyphView ;
}

- help: sender ;
{ // open up a help workspace, "permanently" located in
  // the {NuPath}/workspaces/help.wsd
 return [NXApp showHelpPanel: self] ;
}

- hide: sender ;
{ [NXApp hide: self] ;
  return self ;
}

- (BOOL) inspecting ;
{ // return YES iff instance manager is onscreen 
  return [instanceManager isVisible] ;
}

- instanceManager ;
{ return instanceManager ;
}

- instance: sender ;
{ // bring instanceManager onscreen
  [instanceManager makeKeyAndOrderFront: self] ;
  return self ;
}

- jump: sender ;
{ // scroll selection to visible
  id aWin ;
  if([(aWin = [NXApp mainWindow]) isKindOf:
      [WorkspaceManager class]])
  [[aWin textView] scrollSelToVisible] ;
  return self ;
}

- loadedClassesBrowser ;
{ return loadedClassesBrowser ;
}

- mainMenu ;
{ return mainMenu ;
}

- musicKitManager ;
{ // return mKManager object
  return mKManager  ;  
}


- musicKit: sender ;
{ // bring up the music kit config panel 
  [mKPanel makeKeyAndOrderFront: self] ;
  return self ;
}

 
- newDictionary:sender ;
{ // open a new Dictionary Manager
  [[[NXApp loadNibSection:"DictManager.nib" 
         owner: self] center] makeKeyAndOrderFront: self] ;
    return self;
}

- newWorkspace:sender
{ // open a new workspace, set its font
  id aWin, aView ;
  aWin =  [[[NXApp loadNibSection:"WorkspaceManager.nib" 
         owner: self] center] makeKeyAndOrderFront: self] ;
  [aWin init] ; // this is SUPPOSED to be called automatically,
  // by loadNibSection:, but it ain't, so I have to
  // do it explicitly
  aView = [[aWin contentView] findViewWithTag: TEXTVIEWTAG] ;
  [aView setFont: 
    [Font
       newFont: NXGetDefaultValue([NXApp appName],"NXFont")
          size: atof(NXGetDefaultValue([NXApp appName],"NXFontSize"))
         style: 0
      matrix: NX_FLIPPEDMATRIX
    ]
  ] ;
  [aView becomeFirstResponder] ;
  return self;
}



- open:sender
{ // pop up an open panel. Get
  // the results, open a manager on the code
  id openPanel ;
  openPanel = [OpenPanel new] ;
  [openPanel allowMultipleFiles: YES] ;
  // For some unknown reason, the openPanel must have a
  // delegate if it is to allow "directories", and not
  // just files, to be opened.
  [openPanel setDelegate: self] ;
  if([openPanel runModalForTypes: openTypes])
    [self openList: [openPanel filenames] path: [openPanel directory]] ;
  return self;
}

- openList:  (const char * const *) fileNames  path: (const char *) path ;
// open all files in the NULL terminated list of filenames. If
// path is non-null, then it is used as the directory in which to find
// the files.  If null, the fileNames are considered as paths themselves.
{ char fName[512], className[128] ;
  id manager ;
  int i ;
  float x = 100.0, y = 800.0 ;
  while(*fileNames != NULL)
  { char *extension ;
    extension = rindex(*fileNames,'.') ;
    if(path)
      sprintf(fName,"%s/%s",path,*fileNames) ;
    else
      strcpy(fName,*fileNames) ;
    if(!strcmp(extension,".Nu"))
    { NXStream *aStream ;
      id aWin ;
      aStream = NXOpenTypedStreamForFile(fName, NX_READONLY) ; 
      aWin = NXReadObject(aStream) ;         
      NXCloseTypedStream(aStream) ;
      glyphView = [[aWin contentView] findViewWithTag: GLYPHVIEWTAG] ;
      [glyphView fileName: fName] ;
      [glyphView setupArchived] ;
      [aWin makeKeyAndOrderFront: self] ;
    }
    else if(!strcmp(extension,".m"))
    { manager = [NXApp loadNibSection:"ClassManager.nib" 
        owner: self] ;
      [manager init] ; // supposed to be called automatically
      // by loadNibSection, but it ain't, so I do it explicitly
      [manager moveTopLeftTo:x :y] ;
      [manager fileName: fName] ;
      x += 25.0 ; y -= 25.0 ;
      [manager makeKeyAndOrderFront: self] ;
      for(i = 0 ; (*fileNames)[i] != '.'; i++)
        className[i] = (*fileNames)[i] ;  // strip off extension 
      className[i] ='\0' ;
      [manager className: className] ;
      [manager readFile] ;
      [manager display] ;
      [manager makeKeyAndOrderFront: self] ;
    }
    else // open anything else as a workspace 
    { manager = [NXApp loadNibSection:"WorkspaceManager.nib" 
        owner: self] ;
     [manager init] ; // supposed to be called automatically
      // by loadNibSection, but it ain't, so I do it explicitly
      [manager fileName: fName] ;
      [manager moveTopLeftTo:x :y] ;
      [manager fileName: fName] ;
      x += 25.0 ; y -= 25.0 ;
      [manager makeKeyAndOrderFront: self] ;
      [manager readFile] ;
     }
    fileNames++ ;
  }
  return self ;
}

- preferences: sender ;
{ // bring up the preferences panel 
  if(!prefsPanel)
  { [NXApp loadNibSection:"PrefsManager.nib" owner: NXApp] ;
    [prefsPanel setup] ;
    prefsPanel = NXGetNamedObject("PrefsPanel",NXApp) ;
  }
  [prefsPanel makeKeyAndOrderFront: self] ;
  return self ;
}

- quit: sender ;
{ // before quitting, give user chance last chance
  // to save unsaved documents
  id winList, aWin, *editedWins ;
  int i, knt, editedKnt = 0 ;
  winList = [NXApp windowList] ;
  knt = [winList count] ;
  editedWins = alloca(knt * sizeof(id)) ;
  for(i = 0 ; i < knt ; i++)
  { // first, find all the workspaces and class inspectors
    aWin = [winList objectAt: i] ;
    if([aWin isKindOf: [WorkspaceManager class]] && [aWin isDocEdited])
      editedWins[editedKnt++] = aWin ;
  }
  winList = [GlyphView glyphViewList] ;
  knt = [winList count] ;
  for(i = 0 ; i < knt ; i++)
  { aWin = [[winList objectAt: i] window] ;
    // find all edited glyphViews (except the glyphBrowser)
    if((aWin != glyphBrowser) && [aWin isDocEdited])
      editedWins[editedKnt++] = aWin ;
  }
  // now, if anything was edited:
  if(editedKnt)
  { int rval = NXRunAlertPanel(
      "Quit","%d document%s unsaved changes.\n",
           "Review Unsaved","Quit Anyway","Don't Quit", editedKnt,
      editedKnt > 1 ? "s have" :" has") ;
    if(rval == NX_ALERTDEFAULT)
    { // review all unsaved docs
      for(i = 0 ; i < editedKnt ; i++)
        [editedWins[i] performClose: self] ;
    }
    else if (rval == NX_ALERTOTHER)
       return self ; // abort the quit
    // else rval == NX_ALERTALTERNATE ...just quit
  }
  [NXApp terminate: self] ;
  return self ;
}

- setup ;
{ id prefsManager ;
  const char *nuPath ;
  struct stat statBuf ;
  // perform initialization
  extern void defSizerCode() ;
  // initialize preferences
  [NXApp loadNibSection:"PrefsManager.nib" owner: NXApp] ;
  prefsPanel = NXGetNamedObject("PrefsPanel",NXApp) ;
  prefsManager = NXGetNamedObject("PrefsManager",NXApp) ;

  [prefsManager setup] ;
  if(!strcmp(NXGetDefaultValue([NXApp appName],"NuPath"),""))
    [prefsManager setNuPath] ; // force user to set this
  // now try to verify that NuPath is correct..
  nuPath = NXGetDefaultValue([NXApp appName],"NuPath") ;
  while(stat(nuPath,&statBuf) == -1)
  { [prefsManager setNuPath] ;
    nuPath = NXGetDefaultValue([NXApp appName],"NuPath") ;
  }
  // create zone for allocation glyphs
  glyphZone = NXCreateZone(vm_page_size * 8, vm_page_size * 2, 1) ;
  // load the system classes
  [ClassManager load: "Glyph"] ;
  [ClassManager load: "Error"] ;
  [ClassManager load: "Tray"] ;
  [ClassManager load: "Root"] ;
  // get music kit stuff
  [NXApp loadNibSection:"MKManager.nib" owner: NXApp] ;
  mKPanel = NXGetNamedObject("MKPanel",NXApp) ;
  mKManager = NXGetNamedObject("MKManager",NXApp) ;
  // get the glyph manager
  [NXApp loadNibSection:"GlyphManager.nib" owner: NXApp]  ;
  glyphBrowser = NXGetNamedObject("GlyphBrowser",NXApp) ;
  glyphManager = NXGetNamedObject("GlyphManager",NXApp) ;
  [glyphManager setup] ;
  // load the finder manager
  [NXApp loadNibSection:"FinderManager.nib" owner: NXApp] ;
  finderPanel = NXGetNamedObject("FinderPanel",NXApp) ;
  [NXGetNamedObject("FinderManager",NXApp) init] ;
  // load the instanceManager
  [NXApp loadNibSection:
    "InstanceManager.nib" owner: NXApp] ;
  instanceManager = NXGetNamedObject("InstanceManager",NXApp) ;
  [instanceManager init] ; // supposed to be called automatically
  // by loadNibSection, but it ain't, so I do it explicitly
  return self ;
}

- setTranscriptText: anObject ;
{ // set the transcript text object and set its
  // font size via defaults database
  transcriptText = [anObject docView] ;
  [transcriptText setFont: 
    [Font
       newFont: NXGetDefaultValue([NXApp appName],"NXFont")
          size: atof(NXGetDefaultValue([NXApp appName],"NXFontSize"))
         style: 0
      matrix: NX_FLIPPEDMATRIX
    ]
  ] ;
  return self ;
}

- showLoadedClasses: sender ;
{ [loadedClassesBrowser loadColumnZero] ;
  [loadedClassesWindow orderFront: self] ;
  return self ;
}

- showPs: sender ;
{ // toggle the showps state
  if(showPs)
  { showPs = NO ;
    [[sender selectedCell] setTitle: "Show PS"] ;
  }
  else
  { showPs = YES ;
    [[sender selectedCell] setTitle: "Don't Show PS"] ;
  }
  return self ;
}

- showTranscript: sender ;
{ [transcriptWindow orderFront: self] ;
  return self ;
}

- transcriptText ;
{ return transcriptText ;
}

- unloadAllclasses: sender ;
{ [ClassManager unloadAll] ; 
  return self ;
}


- unloadSelectedClasses: sender ;
{ if([loadedClassesBrowser selectedColumn] == 0)
  { // send unloadClass: to all selected cells
    [[loadedClassesBrowser matrixInColumn: 0] 
      sendAction: @selector(unloadClass:) to: self
      forAllCells: NO] ;
  }
  return self ;
}


- unloadClass: aCell
{ // unload the class which is the stringvalue of aCell.
  // This is called via unloadSelectedClasses:
  [ClassManager unload: [aCell stringValue]] ;
  return self ;
}

- windowDidBecomeMain:sender ;
{ // delegated from WorkspaceManager.m
  // Short-circuit instance Managers...they are workspaces
  // but they don't have a lineNumBrowser...
  if([[NXApp mainWindow] isKindOf: [InstanceManager class]])
    return self ;
  // otherwise if the mainWindow is a class manager or 
  // a workspace manager...
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { if(finderPanel)
    [[NXGetNamedObject("FinderManager",NXApp) lineNumBrowser] loadColumnZero] ;
  }
  return self ;
}

- windowWillClose: sender ;
{ // I am the delegate for workspaces and class
  // inspectors...this gets called before they 
  // close. Must deallocate the window's lineList
  if([sender isDocEdited])
  { int rval ;
    char *fileName, untitled[] = "Untitled" ;
    [sender makeKeyAndOrderFront: self] ;
    fileName = [sender fileName] ;
    if(!fileName)
       fileName = untitled ;
    rval = NXRunAlertPanel(
     "Nutation","Save changes to %s?",
     "Save", "Don't Save","Don't Close",fileName) ;
    if(rval == NX_ALERTDEFAULT) 
      [sender save: self] ;
     else if(rval == NX_ALERTOTHER)
      return nil ;
    // else rval == NX_ALERTALTERNATE
  }
  [[sender lineList] free] ;
  [NXApp removeWindowsItem: sender] ;
  return self ;
}

/**
 ** "public" methods
 ** accessed by messages to
 ** the global object Nu (sole
 ** instance of class MenuManager!)
 ** The .h defs for these method are in
 ** the file "Nutation.h"
 **/


- loadIfNeeded: (char *) aClass ;
{ // load aClass if it is not already loaded
  if([ClassManager loadIndex:aClass] == -1)
    return [ClassManager load: aClass] ;
  return self ;
}

- highLightTarget: (BOOL) YESorNO ;
{ return [glyphView highLightTarget: YESorNO] ;
}

// glyph access

- rootGlyph ;
{ // return target of current or most recently
  // selected glyphView
  return [glyphView rootGlyph] ;
}

- targetGlyph ;
{ // return target of current or most recently
  // selected glyphView
  return [[glyphView rootGlyph] targetGlyph] ;
}

- terminator ;
{ // return a terminator
  return [Terminator new] ;
}

- newGlyphWindow:sender ;
{ // open a new Glyph window, return pointer
  // to it's glyphView
  id aWin ;
  aWin = [[NXApp loadNibSection:"GlyphWindow.nib" 
         owner: self] makeKeyAndOrderFront: self] ;
  return [[aWin contentView]findViewWithTag: GLYPHVIEWTAG] ;
}


- glyphWindow ;
{ return [self newGlyphWindow: self] ;
}

- printf: (char *) format, ... ;
{ // format (as in printf) and print into the transcript
  NXStream *aStream ;
  char *textBuf ;
  int textLen, maxLen ;
  NXSelPt start, end ;
  va_list argList ;
  aStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
  va_start(argList, format) ;
  NXVPrintf(aStream, format, argList) ;
  va_end(argList) ;
  NXGetMemoryBuffer(aStream, &textBuf, &textLen, &maxLen);
  textLen = [transcriptText textLength] ;
  [transcriptText getSel: &start :&end] ; // remember selection
  [transcriptText setSel: textLen :textLen] ;
  [transcriptText replaceSel: textBuf] ;
  if(start.cp == -1 || start.cp == textLen)
    [transcriptText scrollSelToVisible] ;
  else
    [transcriptText setSel: start.cp :end.cp] ;
  NXCloseMemory(aStream,NX_TRUNCATEBUFFER) ;
  return self ;
}

// postscript methods

- ps: (char *) format, ... ;
{ // format (as in printf) and send to windowserver.
  // Done in a separate context to provide good
  // error handling and recovery.  The idea is stolen
  // from Yap. The separate context is only used
  // if we are not printing.
  // Although "dangerous", the use of vsprint 
  // and bigBuf is so much quicker than using a
  // NXVPrintf and an NXStream that I had to
  // go for it. bigBuf is big, but there is no
  // guarantee it is big enough to hold the
  // result of vprintf. 
  NXStream *errorStream ;
  va_list argList ;
  char *dataBuffer ;
  int len, maxLen, transLen ;
  NXHandler exception;
  static DPSContext NUContext = NULL; // The second context
  DPSContext curContext = DPSGetCurrentContext();
  void SwitchContextsWithFocus() ;

  if(!NUContext && (NXDrawingStatus != NX_PRINTING)) // create second context
  { const char *app = [NXApp appName];
    NUContext = DPSCreateContext(NXGetDefaultValue(app, "NXHost"),
        NXGetDefaultValue(app, "NXPSName"), NULL, NULL);
    DPSSetContext(curContext);
  }
  va_start(argList, format) ;
  vsprintf(bigBuf, format, argList) ;
  va_end(argList) ;
  len = strlen(bigBuf) ;
  // make sure this ps code is separated from next ps code
  bigBuf[len++] = '\n' ; 
  if(showPs) 
  { transLen = [transcriptText textLength] ;
    [transcriptText setSel: transLen :transLen] ;
    [transcriptText replaceSel: bigBuf] ;
  }
  if(NXDrawingStatus != NX_PRINTING)
    SwitchContextsWithFocus(NUContext);
  exception.code = 0 ;
  NX_DURING
    if(NXDrawingStatus != NX_PRINTING)
      DPSWriteData(NUContext,bigBuf,len) ;
    else
      DPSWriteData(curContext,bigBuf,len) ;      
    NXPing (); 
  NX_HANDLER
    exception = NXLocalHandler ;
  NX_ENDHANDLER
  if(NXDrawingStatus != NX_PRINTING)
    DPSSetContext(curContext);
  if(exception.code)
  { NXRunAlertPanel("Nu","Postcript errors in message\n"
       "to [ps...] : see transcript",NULL,NULL,NULL) ;
    errorStream = NXOpenMemory(NULL,0,NX_WRITEONLY);
    DPSPrintErrorToStream(errorStream,
       (DPSBinObjSeq) exception.data2);
    NXPrintf(errorStream,"\n\0") ;
    NXFlush(errorStream);
    NXGetMemoryBuffer(errorStream, &dataBuffer, &len, &maxLen);
    [Nu printf: "%s\n", dataBuffer] ;
    NXCloseMemory(errorStream, NX_TRUNCATEBUFFER);
    if(NXDrawingStatus != NX_PRINTING)
    { DPSDestroyContext(NUContext);
      NUContext = NULL;
    }
  }
  return self;
}


- showString: (char *) aString font: (char *) aFont
      width: (float)width height: (float) height
      x: (float) x y: (float) y ;
{ // print aString to currently focused view, using font aFont scaled to [width,height],
  // with origin at x,y
  NPSshowstring(aFont,width,height,x,y,aString) ;
  return self ;
}

// music kit parameters

- (double) samplingRate ;
{ return [mKManager samplingRate] ;
}

- (double) beatsPerMinute ;
{ return [mKManager beatsPerMinute] ;
}

- (double) tickPeriod ;
{ return [mKManager tickPeriod] ;
}

// bitmap management

- bitMapForClass: (char *) aClass size: (NXSize *) aSize ;
{ return [BMList newForClass: aClass size: aSize] ;
}


@end

/**
 ** Postscript stuff
 **/
void SwitchContextsWithFocus(DPSContext newContext)
{ // borowsed from YAP: switch to newContext in order to
  // protect the main context
  float c1x, c1y, c2x, c2y;
  float winCTM[6];
  int realWinNum;
  GetFocus(&c1x, &c1y, &c2x, &c2y, winCTM, &realWinNum);
  DPSSetContext(newContext);
  ReFocus (realWinNum, winCTM, c1x, c1y, c2x, c2y);
}
